package com.ujson.base.persistence.impl;

import com.ujson.base.persistence.AbstractDao;
import com.ujson.base.persistence.CreateDo;
import com.ujson.base.persistence.UpdateDo;
import lombok.Data;
import org.springframework.data.jpa.repository.JpaContext;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.List;

@Data
@Transactional
public abstract class AbstractDaoImpl<T, ID extends Serializable> implements AbstractDao<T, ID> {
    protected EntityManager em;
    protected JpaEntityInformation<T, ?> entityInformation;
    protected Class<T> clazz;

    public AbstractDaoImpl(JpaContext context, Class<T> clazz) {
        this.em = context.getEntityManagerByManagedType(clazz);
        this.clazz = clazz;
        this.entityInformation = JpaEntityInformationSupport.getEntityInformation(clazz, em);
    }

    private String getDeleteQuery() {
        return String.format("delete from %s where id='%s'", entityInformation.getEntityName(), "%s");
    }

    private String getCountQuery() {
        return String.format("select count(%s) from %s x", "1", entityInformation.getEntityName());
    }

    public T save(T obj) {
        if (obj instanceof CreateDo) {
            ((CreateDo)obj).setCreateTime(System.currentTimeMillis());
        }
        if (obj instanceof UpdateDo) {
            ((UpdateDo)obj).setUpdateTime(System.currentTimeMillis());
        }
        em.persist(obj);
        return obj;
    }

    public void delete(T obj) {
        em.remove(obj);
    }

    public int deleteById(ID id) {
        Assert.notNull(id, "The give id must be not null.");
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaDelete<T> criteriaDelete = cb.createCriteriaDelete(getClazz());
        Root<T> root = criteriaDelete.from(getClazz());
        return em.createQuery(criteriaDelete.where(cb.equal(root.get("id"), id))).executeUpdate();
    }

    public T update(T obj) {
        Assert.notNull(obj, "The give entity object must be not null.");
        if (obj instanceof UpdateDo) {
            ((UpdateDo)obj).setUpdateTime(System.currentTimeMillis());
        }
        return em.merge(obj);
    }

    public T findById(ID id) {
        Assert.notNull(id, "The give id must be not null.");
        return em.find(clazz, id);
    }

    public List<T> find(T example) {
        return null;
    }

    public long count() {
        return em.createQuery(getCountQuery(), Long.class).getSingleResult();
    }

    /** just test criteria */
    @Deprecated
    public long countByCriteria() {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Tuple> criteriaQuery = cb.createQuery(Tuple.class);
        Root<T> root = criteriaQuery.from(getClazz());
        Tuple tuple = em.createQuery(criteriaQuery.multiselect(cb.count(root.get("id")))).setMaxResults(1).getSingleResult();
        return Long.parseLong(tuple.get(0).toString());
    }

    public long count(T obj) {
        return 0;
    }

    public List<T> queryList() {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = cb.createQuery(getClazz());
        return em.createQuery(criteriaQuery).getResultList();
    }
}
